-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First contract version #1
base: master
Are you sure you want to change the base?
Conversation
Staking app registers locks per user, not globally.
Computed result was not being used because of memory variable usage.
Besides, own usage lock register was not being freed.
Closes #2.
Locks now have start date too. Modified events in tests. `unlockAndMoveTokens` renamed to `unlockPartialAndMoveTokens`.
1248fb7
to
4ba4500
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work here! Found some things that we should review
|
||
bytes32 constant public CREATE_VOTE_ROLE = keccak256("CREATE_VOTE_ROLE"); | ||
|
||
// TODO!!! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What needs to be done here?
using SafeMath64 for uint64; | ||
|
||
IStaking public staking; | ||
uint256 public voteQuorum; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a better name for this is passingThreshold
or supportRequiredPct
(as in Voting.sol) to better signal that it doesn't have anything to do with the proportion of holders that need to vote, but the relative forVotes / totalVotes
|
||
IStaking public staking; | ||
uint256 public voteQuorum; | ||
uint256 public minorityBlocSlash; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property should be able to be modified, as it doesn't fundamentally change the properties of a vote, just the incentive for voters to be in the winning side.
mapping(address => UserVote) userVotes; | ||
uint256 votesFor; | ||
uint256 votesAgainst; | ||
uint256 totalVotes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If totalVotes
is always for
+ against
then it is better not to store it and just compute it every time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed! 👍
* @notice Create a new vote about "`_metadata`" | ||
* @param _script EVM script to be executed on approval | ||
* @param _metadata Vote metadata | ||
* @return voteId Id for newly created vote |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small typo: Id for the newly created vote
} | ||
|
||
// unlock own tokens | ||
staking.unlock(msg.sender, userVote.lockId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now the app will only unlock if you reveal your vote. We need a way for people to unlock their tokens in these three cases:
- They forgot to reveal their vote
- They created a lock but never ended up committing a vote
- They committed a vote and change their mind before the reveal period starts (in case they want to remove their vote or vote for the other option)
I think a good way to implement this would be for usedLocks
to keep track of the voteId where the lock has been used. If it is 0 then it hasn't been used (would require moving the first index of votes to 1). By having the reference to the voteId
where a given lockId
we can have an removeLock()
function will unlock depending on the case:
- If the reveal period of the vote is over, directly unlock (as revealing would have deleted the reference from the
lockId
to thevoteId
). Otherwise revert. - Directly unlock if the
voteId
for alockId
is 0. - If the reveal period hasn't begun, remove the committed vote, return tokens from slashPool and unlock. Otherwise revert.
We need to think if there are any other scenarios in which tokens could be stuck forever in the contract. I think we should be okey with this + the ability to claim tokens on behalf of other addresses. But let's keep thinking about other edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the third case could be split to its own function as it has further consequences than just unlocking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I just realized that users can unlock tokens in cases 1 and 2 because the lock is only required to be there during the reveal period. I still think we have to build the removeLock()
function in case the start and end date of the locks are set incorrectly.
This realization brings me a different problem, which is that right now a staking lock can be unlocked by anyone outside of the locking period: https://github.com/aragon/aragon-apps/blob/b7fbaead3ad157db508b98092e8df73b9a6f6627/future-apps/staking/contracts/Staking.sol#L437 This is an issue as if you create a lock that starts right when the reveal period starts, someone could perpetually remove your locks before you have the chance to commit to a vote. I think this is an issue with Staking beyond the interaction with PLCR that we should solve.
Also while revealing we would need to check that the lock is still in place, because a valid lock at the time of commitment may get unlocked before reveal and given that we don't check the lock when revealing the vote, someone could double vote.
I think the best solution is requiring users to have a lock that started before the moment they commit a vote (rather than have the lock start time at the beginning of the reveal window) and allow them to remove locks from the PLCR app in those 3 cases, which still make tokens liquid at all times except during the reveal period until they reveal.
assert.isTrue(checkUnlockedPartial(receipt, voter1, app.address, lockId, slashPool)) | ||
assert.isTrue(checkMovedTokens(receipt, voter1, app.address, slashPool)) | ||
const userVote = await app.getUserVote.call(voteId, voter1) | ||
assert.equal(userVote[0].toString(), lockId, "lockId should match") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too similar to the previous test, could probably extract into a function and not repeat so much code?
staking.unlockPartialAndMoveTokens(msg.sender, _lockId, address(this), slashStake); | ||
vote.slashPool = vote.slashPool.add(slashStake); | ||
|
||
vote.userVotes[msg.sender] = UserVote({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should check that the voter hasn't already committed before, as the second commitment will override the first commitment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just brought this up :)
return { voteId: voteId, receipt: receipt } | ||
} | ||
|
||
const revealVote = async (voter, voteOption) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be named commitAndReveal
vote for clarity of what the function is really doing.
import "@aragon/apps-staking/contracts/interfaces/IStaking.sol"; | ||
|
||
|
||
contract StakingMock is IStaking { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note: Love how this nice mock allows for easier unit testing of PLCR. We will need to do proper integration testing for all the user stories once we put everything together to ensure it all works as expected with real staking!
* @param _metadata Vote metadata | ||
* @return voteId Id for newly created vote | ||
*/ | ||
function newVote(bytes _script, string _metadata) isInitialized external auth(CREATE_VOTE_ROLE) returns (uint256 voteId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script is not saved and therefore it is never executed. PLCR should be a forwarder.
uint256 lockId; | ||
uint256 stake; | ||
bytes32 secretHash; | ||
bool revealed; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great call adding revealed
. Thoughts on also including a field to determine if a user has committed a vote? ie
bool didCommit; /// indicates whether an address committed a vote
This can then also be leveraged as a secondary validation (sanity check) in the voting process to guard against double voting. Also takes some of the pressure off strictly relying on voteOption
uint64 revealEndDate; | ||
bool result; | ||
bool computed; | ||
string metadata; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on splitting metadata
into it's own separate struct? My concern is that the field currently enables a wide spectrum of possible input. A metadata struct would enable a little more structure.
struct Metainformation {
string title;
string description;
}
This is assuming we can align on fields, if not.. I don't think it's necessary
vote.revealEndDate = vote.commitEndDate.add(revealDuration); | ||
vote.metadata = _metadata; | ||
|
||
NewVote(voteId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be emitting an event here? Also, I think we have a couple other unused events.
Vote storage vote = votes[_voteId]; | ||
UserVote storage userVote = votes[_voteId].userVotes[msg.sender]; | ||
|
||
// check reveal period |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI - can use the didCommit
field here that I mentioned above as well.
require(!didCommit(_address, _pollID));
UserVote storage userVote = votes[_voteId].userVotes[msg.sender]; | ||
|
||
// check reveal period | ||
require(vote.commitEndDate < getTimestamp()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we could aggregate these into a commitPeriodActive
bool on UserVote
@@ -120,11 +120,11 @@ contract PLCR is AragonApp, IVoting { | |||
require(getTimestamp() <= vote.commitEndDate); | |||
|
|||
// check lock and get amount | |||
uint256 stake = checkLock(msg.sender, _lockId, vote.revealEndDate); | |||
uint256 stake = checkLock(msg.sender, _lockId, vote.commitEndDate, vote.revealEndDate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not enough: the user could unlock the remaining tokens (stake - slashStake
) to another account before the reveal period starts (i.e., the commit period ends) and vote again.
No description provided.